all repos — caroster @ 01cb299b08e873d95e7352a5e023658147653118

[Octree] Group carpool to your event https://caroster.io

frontend/pages/e/[uuid]/details.tsx (view raw)

  1import moment from 'moment';
  2import Linkify from 'linkify-react';
  3import Tooltip from '@mui/material/Tooltip';
  4import IconButton from '@mui/material/IconButton';
  5import Box from '@mui/material/Box';
  6import Link from '@mui/material/Link';
  7import Card from '@mui/material/Card';
  8import Container from '@mui/material/Container';
  9import TextField from '@mui/material/TextField';
 10import Typography from '@mui/material/Typography';
 11import TuneIcon from '@mui/icons-material/Tune';
 12import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
 13import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
 14import {useTheme} from '@mui/material/styles';
 15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
 16import {PropsWithChildren, useState} from 'react';
 17import {useTranslation} from 'next-i18next';
 18import pageUtils from '../../../lib/pageUtils';
 19import DetailsLink from '../../../containers/DetailsLink';
 20import ShareEvent from '../../../containers/ShareEvent';
 21import PlaceInput from '../../../containers/PlaceInput';
 22import LangSelector from '../../../components/LangSelector';
 23import usePermissions from '../../../hooks/usePermissions';
 24import useEventStore from '../../../stores/useEventStore';
 25import useToastStore from '../../../stores/useToastStore';
 26import EventLayout, {TabComponent} from '../../../layouts/Event';
 27import {
 28  EventByUuidDocument,
 29  useUpdateEventMutation,
 30} from '../../../generated/graphql';
 31import {langLocales} from '../../../locales/constants';
 32import {getLocaleForLang} from '../../../lib/getLocale';
 33
 34interface Props {
 35  eventUUID: string;
 36  announcement?: string;
 37}
 38
 39const Page = (props: PropsWithChildren<Props>) => {
 40  return <EventLayout {...props} Tab={DetailsTab} />;
 41};
 42
 43const DetailsTab: TabComponent<Props> = ({}) => {
 44  const {t} = useTranslation();
 45  const {
 46    userPermissions: {canEditEventDetails},
 47  } = usePermissions();
 48  const theme = useTheme();
 49  const [updateEvent] = useUpdateEventMutation();
 50  const addToast = useToastStore(s => s.addToast);
 51  const setEventUpdate = useEventStore(s => s.setEventUpdate);
 52  const event = useEventStore(s => s.event);
 53  const [isEditing, setIsEditing] = useState(false);
 54
 55  if (!event) return null;
 56
 57  const hasGeoloc = event.latitude && event.longitude;
 58
 59  const onSave = async e => {
 60    try {
 61      const {uuid, ...data} = event;
 62      const {
 63        id,
 64        travels,
 65        waitingPassengers,
 66        __typename,
 67        administrators,
 68        passengers,
 69        ...input
 70      } = data;
 71      await updateEvent({
 72        variables: {
 73          uuid,
 74          eventUpdate: {
 75            ...input,
 76          },
 77        },
 78        refetchQueries: ['eventByUUID'],
 79      });
 80      setIsEditing(false);
 81    } catch (error) {
 82      console.error(error);
 83      addToast(t('event.errors.cant_update'));
 84    }
 85  };
 86
 87  const modifyButton = isEditing ? (
 88    <Tooltip
 89      title={t('event.details.save')}
 90      sx={{
 91        position: 'absolute',
 92        top: theme.spacing(2),
 93        right: theme.spacing(2),
 94      }}
 95    >
 96      <IconButton color="primary" onClick={onSave}>
 97        <CheckCircleOutlineIcon />
 98      </IconButton>
 99    </Tooltip>
100  ) : (
101    <Tooltip
102      title={t('event.details.modify')}
103      sx={{
104        position: 'absolute',
105        top: theme.spacing(2),
106        right: theme.spacing(2),
107      }}
108    >
109      <IconButton color="primary" onClick={() => setIsEditing(true)}>
110        <TuneIcon />
111      </IconButton>
112    </Tooltip>
113  );
114
115  return (
116    <Box
117      sx={{
118        position: 'relative',
119      }}
120    >
121      <Container
122        sx={{
123          p: 4,
124          mt: 6,
125          mb: 11,
126          mx: 0,
127          [theme.breakpoints.down('md')]: {
128            p: 2,
129          },
130        }}
131      >
132        <Card
133          sx={{
134            position: 'relative',
135            maxWidth: '100%',
136            width: '480px',
137            p: 2,
138          }}
139        >
140          <Typography variant="h4" pb={2}>
141            {t('event.details')}
142          </Typography>
143          {canEditEventDetails() && modifyButton}
144          {(isEditing || event.name) && (
145            <Box pt={2} pr={1.5}>
146              <Typography variant="overline">
147                {t('event.fields.name')}
148              </Typography>
149              <Typography>
150                {isEditing ? (
151                  <TextField
152                    size="small"
153                    fullWidth
154                    value={event.name}
155                    onChange={e => setEventUpdate({name: e.target.value})}
156                    name="name"
157                    id="EditEventName"
158                  />
159                ) : (
160                  <Typography id="EventName">{event.name}</Typography>
161                )}
162              </Typography>
163            </Box>
164          )}
165          {(isEditing || event.date) && (
166            <Box pt={2} pr={1.5}>
167              <Typography variant="overline">
168                {t('event.fields.date')}
169              </Typography>
170              {isEditing ? (
171                <Typography>
172                  <DatePicker
173                    slotProps={{
174                      textField: {
175                        size: 'small',
176                        id: `EditEventDate`,
177                        fullWidth: true,
178                        placeholder: t('event.fields.date_placeholder'),
179                      },
180                    }}
181                    format="DD/MM/YYYY"
182                    value={moment(event.date)}
183                    onChange={date =>
184                      setEventUpdate({
185                        date: !date ? null : moment(date).format('YYYY-MM-DD'),
186                      })
187                    }
188                  />
189                </Typography>
190              ) : (
191                <Box position="relative">
192                  <Typography id="EventDate">
193                    {moment(event.date).format('DD/MM/YYYY')}
194                  </Typography>
195                </Box>
196              )}
197            </Box>
198          )}
199          {(isEditing || event.address) && (
200            <Box pt={2} pr={1.5}>
201              <Typography variant="overline">
202                {t('event.fields.address')}
203              </Typography>
204              {isEditing ? (
205                <PlaceInput
206                  place={event.address}
207                  latitude={event.latitude}
208                  longitude={event.longitude}
209                  onSelect={({place, latitude, longitude}) =>
210                    setEventUpdate({
211                      address: place,
212                      latitude,
213                      longitude,
214                    })
215                  }
216                />
217              ) : (
218                <Box position="relative">
219                  <Typography
220                    id="EventAddress"
221                    title={t`placeInput.noCoordinates`}
222                    sx={{
223                      pr: 3,
224                      display: 'inline-flex',
225                      alignItems: 'center',
226                      columnGap: 1,
227                    }}
228                  >
229                    <Link
230                      target="_blank"
231                      rel="noreferrer"
232                      href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
233                        event.address
234                      )}`}
235                      onClick={e => e.preventDefault}
236                    >
237                      {event.address}
238                    </Link>
239                    {!hasGeoloc && (
240                      <InfoOutlinedIcon fontSize="small" color="warning" />
241                    )}
242                  </Typography>
243                </Box>
244              )}
245            </Box>
246          )}
247          {(isEditing || event.description) && (
248            <Box pt={2} pr={1.5}>
249              <Typography variant="overline">
250                {t('event.fields.description')}
251              </Typography>
252              {isEditing ? (
253                <Typography>
254                  <TextField
255                    fullWidth
256                    multiline
257                    maxRows={4}
258                    inputProps={{maxLength: 250}}
259                    value={event.description || ''}
260                    onChange={e =>
261                      setEventUpdate({description: e.target.value})
262                    }
263                    id={`EditEventDescription`}
264                    name="description"
265                  />
266                </Typography>
267              ) : (
268                <Typography
269                  id="EventDescription"
270                  sx={{pr: 3, whiteSpace: 'pre-line'}}
271                >
272                  <Linkify options={{render: DetailsLink}}>
273                    {event.description}
274                  </Linkify>
275                </Typography>
276              )}
277            </Box>
278          )}
279          {(isEditing || event.lang) && (
280            <Box pt={2} pr={1.5}>
281              <Typography variant="overline">
282                {t('event.fields.lang')}
283              </Typography>
284              {isEditing ? (
285                <LangSelector
286                  value={event.lang}
287                  onChange={lang => setEventUpdate({lang})}
288                />
289              ) : (
290                <Typography id="EventLang" sx={{pr: 3}}>
291                  {langLocales[event.lang]}
292                </Typography>
293              )}
294            </Box>
295          )}
296          {!isEditing && (
297            <ShareEvent
298              title={`Caroster ${event.name}`}
299              sx={{width: '100%', mt: 2}}
300            />
301          )}
302        </Card>
303      </Container>
304    </Box>
305  );
306};
307
308export const getServerSideProps = pageUtils.getServerSideProps(
309  async (context, apolloClient) => {
310    const {uuid} = context.query;
311    const {host = ''} = context.req.headers;
312    let event = null;
313
314    // Fetch event
315    try {
316      const {data} = await apolloClient.query({
317        query: EventByUuidDocument,
318        variables: {uuid},
319      });
320      event = data?.eventByUUID?.data;
321    } catch (error) {
322      return {
323        notFound: true,
324      };
325    }
326
327    const description = await getLocaleForLang(
328      event?.attributes?.lang,
329      'meta.description'
330    );
331
332    return {
333      props: {
334        eventUUID: uuid,
335        metas: {
336          title: event?.attributes?.name || '',
337          description,
338          url: `https://${host}${context.resolvedUrl}`,
339        },
340      },
341    };
342  }
343);
344export default Page;